package io.kiki.sba.registry.util;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * generates ID: 49 bit millisecond timestamp + 15 bit incremental ID
 *
 * <p>refer to: https://github.com/twitter/snowflake
 */
public final class DatumVersionUtil {
    static final String DATUM_VERSION_TYPE_CONFREG = "confreg";
    static final String DATUM_VERSION_TYPE_REGISTRY = "registry";
    private static final Logger logger = LoggerFactory.getLogger(DatumVersionUtil.class);
    /**
     * Tue Jan 01 00:00:00 CST 2019
     */
    private static final long twepoch = 1546272000000L;
    /**
     * Tue Jan 01 00:00:00 CST 2021
     */
    private static final long timeStart = 1609459200000L;
    private static final long sequenceBits = 15L;
    private static final long timestampLeftShift = sequenceBits;
    private static final long registryMinVersion = (timeStart - twepoch) << timestampLeftShift;
    private static final long sequenceMask = -1L ^ (-1L << sequenceBits);
    static String datumVersionType = SystemUtils.getSystem("registry.data.datumVersionType", DATUM_VERSION_TYPE_REGISTRY);
    private static long sequence = 0L;
    private static long lastTimestamp = -1L;

    private DatumVersionUtil() {
    }

    public static synchronized long nextId() {
        long timestamp = timeGen();
        if (timestamp < lastTimestamp) {
            throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
        }

        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
                timestamp = untilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }
        lastTimestamp = timestamp;
        return ((timestamp - twepoch) << timestampLeftShift) | sequence;
    }

    public static long getRealTimestamp(long id) {
        if (versionType(id).equals(DATUM_VERSION_TYPE_CONFREG)) {
            return id;
        }
        return (id >> timestampLeftShift) + twepoch;
    }

    static long untilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    public static boolean useConfregVersionGen() {
        return StringUtils.equals(datumVersionType, DATUM_VERSION_TYPE_CONFREG);
    }

    public static long confregNextId(long lastVersion) {
        long timestamp = timeGen();
        if (lastVersion < timestamp) {
            return timestamp;
        }
        if (lastVersion > timestamp + 10) {
            logger.warn("[DatumVersion] last version {} is higher than currentTimestamp {}", lastVersion, timestamp);
        }
        return lastVersion + 1;
    }

    public static long transferDatumVersion(long version) {
        if (version <= 1) {
            // empty datum
            return version;
        }
        if (useConfregVersionGen()) {
            return getRealTimestamp(version);
        }
        return version;
    }


    static String versionType(long version) {
        if (version > registryMinVersion) {
            return DATUM_VERSION_TYPE_REGISTRY;
        }
        return DATUM_VERSION_TYPE_CONFREG;
    }

    private static long timeGen() {
        return System.currentTimeMillis();
    }
}
